昨天所討論的是在實際實作中該如何讓程式符合預期的時間複雜度,而今天要進一步聊聊該如何讓程式表現的比想像更好!
前幾天的文章在介紹複雜度時,曾提過一些複雜度函數中如 $f(n)=n^2+5n+3$ 之中的 $5n+3$ 是可以被忽略的,因為它是對整體影響沒那麼大的「常數因子」。說的更白話一點就是:常數是一些會影響程式速度、但對時間複雜度沒有影響的變因。
但是常數真的不重要嗎?漸進複雜度取出了對整體步驟數影響最大的因子作為代表,因此當輸入的量級變大時常數對時間的影響的確可以忽略;但是要是輸入沒那麼大呢?在小範圍輸入下有可能常數反而才是對執行時間影響最為重要的因子!
因此要設計高效能的程式時,除了考慮時間複雜度本身以外,還須注意不能把常數寫得太大!
以下將針對 C++ 介紹幾點優化常數的方法:
選擇合適的編譯環境可以大幅提升程式的效能。例如,GCC 和 Clang 是兩個常用的 C++ 編譯器,它們有不同的優化算法和機制。通常,Clang 的錯誤訊息更為清晰,但 GCC 在某些情況下可能會產生更高效的機器碼。
編譯參數也是一個重要的考慮因素。例如,使用 -O2 或 -O3 參數可以開啟更多的編譯優化。另外,特定的硬體加速(如 GPU 或 SIMD 指令集)也可透過編譯參數來啟用。
選擇正確的數據類型和操作可以減少不必要的計算量。例如,使用位運算代替數學運算(如 n << 1 代替 n * 2)可以提供更快的運算速度。
I/O 是許多程式中一個常被忽略但極為重要的效能瓶頸。使用 scanf 和 printf 通常比 C++ 的 cin 和 cout 更快,尤其是當你使用 ios::sync_with_stdio(false); 來取消同步後。
了解 CPU 的 cache 機制也是優化的一部分。適當地安排數據結構和存取模式可以減少 cache miss,從而提高效能。
總結來說,常數對於程式效能不僅僅是一個「可有可無」的選項。在實際應用中,尤其是數據量不大的情況下,一個很低的常數因子可能會讓你的程式從「可用」變為「出色」。因此,在設計算法和實現程式時,除了考慮漸進複雜度,也不應忽視這些看似次要但實則關鍵的因素。